<?php


namespace App\Services;


use App\Constants\SmsConstant;
use App\Models\SmsLog;
use App\Models\SmsTpl;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Overtrue\EasySms\EasySms;
use Overtrue\EasySms\Exceptions\NoGatewayAvailableException;
use Overtrue\EasySms\Strategies\OrderStrategy;

class SmsService
{
    protected static $instance;

    protected $config;

    protected $options;

    protected $sms;

    protected $errors = [];

    protected $maxExpire = 0;

    protected $maxCount = 0;

    public static function instance($options = [])
    {
        if (is_null(self::$instance)) {
            self::$instance = new static($options);
        }

        return self::$instance;
    }

    public function __construct($options = [])
    {
        $this->config = $config = getSettings(SmsConstant::GROUP);
        $this->maxExpire = $this->config[SmsConstant::MAX_EXPIRE] ?? 0;
        $this->maxCount = $this->config[SmsConstant::MAX_COUNT] ?? 0;

        $option = [
            // HTTP 请求的超时时间（秒）
            'timeout'  => 5.0,

            // 默认发送配置
            'default'  => [
                // 网关调用策略，默认：顺序调用
                'strategy' => OrderStrategy::class,

                // 默认可用的发送网关
                'gateways' => [
                    $config['sms_driver'],
                ],
            ],

            // 可用的网关配置
            'gateways' => [
                'errorlog' => [
                    'file' => storage_path('logs/sms/sms-' . date('Y-m-d') . '.log'),
                ],
                'aliyun'   => [
                    'access_key_id'     => $config[SmsConstant::ALIYUN_APP_KEY] ?? '',
                    'access_key_secret' => $config[SmsConstant::ALIYUN_APP_SECRET_KEY] ?? '',
                    'sign_name'         => $config[SmsConstant::ALIYUN_APP_SIGN_NAME] ?? '',
                ],
            ]
        ];

        $this->options = array_merge($option, $options);
        $this->sms = new EasySms($this->options);
    }

    public function send(string $phone, string $type = null, string $code = null): bool
    {
        $this->errors = [];
        $type = $type ?? 'default';
        $code = $code ?? mt_rand(100000, 999999);
        $memberId = auth('api')->user()->id ?? 0;

        $smsTpl = SmsTpl::query()->where('type', $type)->first();
        if (!$smsTpl) {
            $this->errors[] = '找不到短信模板';
            return false;
        }

        try {
            DB::beginTransaction();

            SmsLog::create([
                'phone'       => $phone,
                'type'        => $type,
                'code'        => $code,
                'member_id'   => $memberId,
                'ip'          => request()->ip(),
                'create_time' => time(),
                'extends'     => json_encode([]),
            ]);

            $content = $this->parseDriverTpl($smsTpl->content ?? '');
            $data = ['code' => $code];

            $this->sms->send($phone, [
                'content'  => $this->parseContent($content, $data),
                'template' => $this->parseDriverTpl($smsTpl->template ?? ''),
                'data'     => $data,
            ]);

            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();
            log_exception('发送短信失败', $e);
            $this->errors[] = '发送短信失败';
            return false;
        }

        return true;
    }

    public function sendTemplate(string $phone, string $type, int $memberId, array $data): bool
    {
        $this->errors = [];

        $smsTpl = SmsTpl::query()->where('type', $type)->first();
        if (!$smsTpl) {
            $this->errors[] = '找不到短信模板';
            return false;
        }

        try {
            DB::beginTransaction();

            SmsLog::create([
                'phone'       => $phone,
                'type'        => $type,
                'code'        => $data['code'] ?? '',
                'member_id'   => $memberId,
                'ip'          => request()->ip(),
                'create_time' => time(),
                'extends'     => json_encode(['data' => $data]),
            ]);

            $content = $this->parseDriverTpl($smsTpl->content ?? '');

            $this->sms->send($phone, [
                'content'  => $this->parseContent($content, $data),
                'template' => $this->parseDriverTpl($smsTpl->template ?? ''),
                'data'     => $data,
            ]);

            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();

            is_a($e, NoGatewayAvailableException::class) ?
                log_exception('发送短信失败', $e->getExceptions()) :
                log_exception('发送短信失败', $e);

            $this->errors[] = '发送短信失败';
            return false;
        }

        return true;
    }

    public function check(string $phone, string $code, string $type = null): bool
    {
        if (app()->environment('local') || app()->environment('testing')) {
            return true;
        }

        if (!$code) {
            return false;
        }

        $supperPhone = config('app.supper_phone', []);
        $supperPhoneCode = config('app.supper_phone_code', []);
        if (in_array($phone, []) && in_array($code, $supperPhoneCode)) {
            return true;
        }

        $conditions = [
            'phone'     => $phone,
            'read_time' => 0,
        ];

        if ($type) {
            $conditions['type'] = $type;
        }
        if ($this->maxExpire > 0) {
            $conditions[] = [function ($query) {
                $query->where('create_time', '>=', time() - $this->maxExpire);
            }];
        }
        if ($this->maxCount > 0) {
            $conditions[] = [function ($query) {
                $query->where('verify_count', '<', $this->maxCount);
            }];
        }

        $row = SmsLog::query()->where($conditions)->orderBy('id', 'desc')->first();
        if (!$row || ($this->maxCount > 0 && $row->verify_count >= $this->maxCount)) {
            return false;
        }

        $row->verify_count++;
        if ($row->code == $code) {
            $row->read_time = time();
            $row->save();
            return true;
        }

        $row->save();
        return false;
    }

    public function getErrors(): array
    {
        return $this->errors;
    }

    protected function parseDriverTpl(string $tpl): string
    {
        $driver = $this->config[SmsConstant::SMS_DRIVER];

        $tpl = explode("\r\n", $tpl);
        if (empty($tpl)) {
            return '';
        }

        $arr = [];
        foreach ($tpl as $key => $value) {
            if (empty($value)) {
                continue;
            }
            $value = explode('|', $value);
            $arr[$value[0]] = $value[1] ?? '';
        }

        return isset($arr[$driver]) ? $arr[$driver] : ($arr['default'] ?? '');
    }

    protected function parseContent(string $content, array $data)
    {
        foreach ($data as $key => $value) {
            $content = str_replace('${' . $key . '}', $value, $content);
        }

        return $content;
    }
}
